 /***************************************************************************
 * Copyright (C) 2009
 * by Dimok
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any
 * damages arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any
 * purpose, including commercial applications, and to alter it and
 * redistribute it freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you
 * must not claim that you wrote the original software. If you use
 * this software in a product, an acknowledgment in the product
 * documentation would be appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and
 * must not be misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source
 * distribution.
 *
 * networkops.cpp
 *
 * Network operations
 * for Wii-Xplorer 2009
 ***************************************************************************/
#include <fcntl.h>

#include "../stdafx.h"
#include "http.h"
#include "networkops.h"

static bool networkinit = false;
static char IP[16];
static u8 * ThreadStack = NULL;
static bool firstRun = false;

static lwp_t networkthread = LWP_THREAD_NULL;
static bool networkHalt = true;
static bool exitRequested = false;

/****************************************************************************
 * Initialize_Network
 ***************************************************************************/
void Initialize_Network(void)
{
	if(networkinit)
		return;

	s32 result;

	result = if_config(IP, NULL, NULL, true);

	if(result < 0) {
		networkinit = false;
		return;
	}

	networkinit = true;
	return;
}

/****************************************************************************
 * DeInit_Network
 ***************************************************************************/
void DeInit_Network(void)
{
	if(networkinit)
	{
		net_deinit();
		networkinit = false;
	}
}

/****************************************************************************
 * Check if network was initialised
 ***************************************************************************/
bool IsNetworkInit(void)
{
	return networkinit;
}

/****************************************************************************
 * Get network IP
 ***************************************************************************/
char * GetNetworkIP(void)
{
	return IP;
}

/****************************************************************************
 * HaltNetwork
 ***************************************************************************/
void HaltNetworkThread()
{
	networkHalt = true;

	//! wait for thread to finish
	while(!LWP_ThreadIsSuspended(networkthread))
	{
		usleep(100);
	}
}

/****************************************************************************
 * ResumeNetworkThread
 ***************************************************************************/
void ResumeNetworkThread()
{
	networkHalt = false;
	LWP_ResumeThread(networkthread);
}

/*********************************************************************************
 * Networkthread for background network initialize and update check with idle prio
 *********************************************************************************/
static void * networkinitcallback(void *arg UNUSED)
{
	while(!exitRequested)
	{
		if(networkHalt)
		{
			LWP_SuspendThread(networkthread);
			usleep(100);
			continue;
		}
		
		if(!networkinit)
			Initialize_Network();
		
		if(!firstRun)
		{
			LWP_SetThreadPriority(networkthread, 0);
			firstRun = true;
		}
		
		usleep(200000);
	}
	return NULL;
}

/****************************************************************************
 * InitNetworkThread with priority 0 (idle)
 ***************************************************************************/
void InitNetworkThread()
{
	ThreadStack = (u8 *) memalign(32, 16384);
	if(!ThreadStack)
		return;

	LWP_CreateThread (&networkthread, networkinitcallback, NULL, ThreadStack, 16384, 30);
	
	if(Settings.AutoConnect)
		ResumeNetworkThread();
}

/****************************************************************************
 * ShutdownThread
 ***************************************************************************/
void ShutdownNetworkThread()
{
	exitRequested = true;

	if(networkthread != LWP_THREAD_NULL)
	{
		ResumeNetworkThread();
		LWP_JoinThread (networkthread, NULL);
		networkthread = LWP_THREAD_NULL;
	}
	
	if(ThreadStack)
		free(ThreadStack);
	ThreadStack = NULL;
}

/****************************************************************************
 * Test if connection to the address is available (PING)
 ***************************************************************************/
bool CheckConnection(const char *url, float timeout)
{
	//! Check if the url starts with "http://", if not it is not considered a valid url
	if (strncmp(url, "http://", strlen("http://")) != 0)
		return false;

	//! Locate the path part of the url by searching for '/' past "http://"
	char *path = strchr(url + strlen("http://"), '/');

	//! At the very least the url has to end with '/', ending with just a domain is invalid
	if (path == NULL)
		return false;

	//! Extract the domain part out of the url
	int domainlength = path - url - strlen("http://");
	if (domainlength == 0)
		return false;

	char domain[domainlength + 1];
	strlcpy(domain, url + strlen("http://"), domainlength + 1);

	//! Parsing of the URL is done, start making an actual connection
	u32 ipaddress = getipbynamecached(domain);
	if (ipaddress == 0)
		return false;

	//! Initialize socket
	s32 connection = net_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
	if (connection < 0) return connection;

	s32 flags = net_fcntl(connection, F_GETFL, 0);
	if (flags >= 0) flags = net_fcntl(connection, F_SETFL, flags | 4);

	struct sockaddr_in connect_addr;
	memset(&connect_addr, 0, sizeof(connect_addr));
	connect_addr.sin_family = AF_INET;
	connect_addr.sin_port = 80;
	connect_addr.sin_addr.s_addr = getipbynamecached(domain);

	Timer netTime;

	int res = -1;
	while(res < 0 && res != -127 && netTime.elapsed() < timeout)
	{
		res = net_connect(connection, (struct sockaddr*) &connect_addr, sizeof(connect_addr));
		usleep(1000);
	}

	net_close(connection);

	return !(res < 0 && res != -127);
}

/****************************************************************************
 * Download request
 ***************************************************************************/
int DownloadWithResponse(const char * url, u8 **outbuffer, u32 *outsize)
{
	//!Check if the url starts with "http://", if not it is not considered a valid url
	if (strncmp(url, "http://", strlen("http://")) != 0)
		return -1;

	//!Locate the path part of the url by searching for '/' past "http://"
	char *path = strchr(url + strlen("http://"), '/');

	//!At the very least the url has to end with '/', ending with just a domain is invalid
	if (path == NULL)
		return -1;

	//!Extract the domain part out of the url
	int domainlength = path - url - strlen("http://");

	if (domainlength == 0)
		return -1;

	char domain[domainlength + 1];
	strlcpy(domain, url + strlen("http://"), domainlength + 1);

	int connect = GetConnection(domain);
	if (connect < 0)
		return -1;

	//!Form a nice request header to send to the webserver
	char header[strlen(path) + strlen(domain) + strlen(url) + 100];
	sprintf(header, "GET %s HTTP/1.1\r\nHost: %s\r\nReferer: %s\r\nUser-Agent: SaveGameGX\r\nConnection: close\r\n\r\n", path, domain, url);

	int ret = net_send(connect, header, strlen(header), 0);
	if(ret < 0)
	{
		net_close(connect);
		return ret;
	}

	int blocksize = 4096;

	u8 *buffer = (u8 *) malloc(blocksize);
	if(!buffer)
	{
		net_close(connect);
		return -1;
	}

	int done = 0;

	while(1)
	{
		int ret = network_read(connect, buffer+done, blocksize);
		
		if(ret < 0)
		{
			free(buffer);
			net_close(connect);
			return -1;
		}
		else if(ret == 0)
			break;
		
		done += ret;
		u8 *tmp = (u8 *) realloc(buffer, done+blocksize);
		if(!tmp)
		{
			free(buffer);
			net_close(connect);
			return -1;
		}
		
		buffer = tmp;
	}

	net_close(connect);

	u8 *tmp = (u8 *) realloc(buffer, done+1);
	if(!tmp)
	{
		free(buffer);
		return -1;
	}

	buffer = tmp;
	buffer[done] = 0;

	*outbuffer = buffer;
	*outsize = done;

	return done;
}
